﻿namespace Blaze.UI
{
    using System.Collections.Generic;
    using Framework;
    using UnityEngine;

    /// <summary>
    /// 管理屏幕模糊效果的截屏贴图。
    /// </summary>
    [Singleton]
    public class ScreenBlurSource : MonoBehaviour
    {
        public int Downsample = 2;
        public int MaxIteration = 15;
        public float MaxUpdateRate = 30;

        /// <summary>
        /// 获取<see cref="ScreenBlurSource"/>的唯一实例。
        /// </summary>
        public static ScreenBlurSource Instance
        {
            get { return Singleton.Get<ScreenBlurSource>(); }
        }

        public RenderTexture BlurredScreen
        {
            get { return mRenderTexture; }
        }

        private float minUpdateCircle
        {
            get { return MaxUpdateRate > 0 ? (1 / MaxUpdateRate) : float.PositiveInfinity; }
        }

        /// <summary>
        /// 捕获当前屏幕。
        /// </summary>
        public void Capture()
        {
            init();

            enabled = true;

            prepareRenderTextures();

            mCameras.Clear();
            mCameras.AddRange(Camera.allCameras);
            mCameras.Sort(sortByDepth);

            RenderTexture.active = mRenderTexture;

            for (var i = 0; i < mCameras.Count; i++)
            {
                var c = mCameras[i];
                c.targetTexture = mRenderTexture;
                c.Render();
                c.targetTexture = null;
            }

            RenderTexture.active = null;

            mMaterial.mainTexture = mRenderTexture;
            mIterationTimes = 0;
            progressiveBlur();
        }

        /// <summary>
        /// 预先创建好所需要的<see cref="RenderTexture"/>，避免卡顿。
        /// </summary>
        public void Prewarm()
        {
            prepareRenderTextures();
        }

        private static int sortByDepth(Camera a, Camera b)
        {
            if (a.depth - b.depth > 0)
                return 1;
            if (a.depth - b.depth < 0)
                return -1;
            return 0;
        }

        private void Awake()
        {
            enabled = false;
            init();
        }

        private void init()
        {
            if (mIsInitialized)
                return;

            var shader = Shader.Find("Hidden/Blaze/SimpleBlur");
            if (shader == null)
            {
                Debug.LogError("can't find blur shader");
                return;
            }

            mMaterial = new Material(shader);

            BlazeEngine.Instance.ScreenSizeChanged += onScreenSizeChanged;

            mIsInitialized = true;
        }

        private void OnDestroy()
        {
            if (mRenderTexture != null)
                RenderTexture.ReleaseTemporary(mRenderTexture);
            if (mTempTexture != null)
                RenderTexture.ReleaseTemporary(mTempTexture);
        }

        private void onScreenSizeChanged()
        {
            mShouldRecreateRT = true;
        }

        private void prepareRenderTextures()
        {
            var width = Screen.width >> Downsample;
            var height = Screen.height >> Downsample;

            if (mRenderTexture == null)
            {
                mRenderTexture = RenderTexture.GetTemporary(width, height, 0);
                mRenderTexture.filterMode = FilterMode.Bilinear;
                mTempTexture = RenderTexture.GetTemporary(width, height, 0);
                mRenderTexture.filterMode = FilterMode.Bilinear;
            }
            else if (mShouldRecreateRT)
            {
                mRenderTexture.Release();
                mTempTexture.Release();
                mRenderTexture = RenderTexture.GetTemporary(width, height, 0);
                mRenderTexture.filterMode = FilterMode.Bilinear;
                mTempTexture = RenderTexture.GetTemporary(width, height, 0);
                mRenderTexture.filterMode = FilterMode.Bilinear;
                mShouldRecreateRT = false;
            }
        }

        private void progressiveBlur()
        {
            if (mIterationTimes > MaxIteration)
                return;
            mIterationTimes++;
            if (mRenderTexture.IsCreated())
                mRenderTexture.DiscardContents();
            if (mTempTexture.IsCreated())
                mTempTexture.DiscardContents();

            Graphics.Blit(mRenderTexture, mTempTexture);
            Graphics.Blit(mTempTexture, mRenderTexture, mMaterial);
            mLastUpdateTime = Time.realtimeSinceStartup;
        }

        private void Update()
        {
            var now = Time.realtimeSinceStartup;
            if (now - mLastUpdateTime >= minUpdateCircle)
                progressiveBlur();
        }

        private readonly List<Camera> mCameras = new List<Camera>();
        private bool mIsInitialized;
        private int mIterationTimes;
        private float mLastUpdateTime;
        private Material mMaterial;
        private RenderTexture mRenderTexture;
        private bool mShouldRecreateRT;
        private RenderTexture mTempTexture;
    }
}